using System;
using System.Collections.Generic;
using Framework.Core;
using UnityEngine;
using UnityEngine.AI;

public class MapGrid
{
    public float GridSize { get; private set; }
    public int Width { get; private set; }
    public int Height { get; private set; }
    private CustomPool<CellData> _cellPool;
    private List<CellData> _list;
    private Dictionary<int, int> _idToIndex;
    private int _count;
    private Vector3 _min;
    public MapGrid()
    {
        _cellPool = new CustomPool<CellData>(() => { return new CellData(); });
        _list = new List<CellData>();
        _idToIndex = new Dictionary<int, int>();
    }
    public void Set(Vector3 min, Vector3 max, float gridSize)
    {
        _min = min;
        _min.y = 0f;
        var w = max.x - min.x;
        var h = max.z - min.z;
        this.GridSize = gridSize;
        this.Width = Mathf.CeilToInt(w / gridSize);
        this.Height = Mathf.CeilToInt(h / gridSize);
        Clear();
        _count = this.Width * this.Height;
        var len = _count;
        while (len-- > 0)
        {
            _list.Add(null);
        }
    }
    public void Clear()
    {
        var len = _list.Count;
        while (len-- > 0)
        {
            var cell = _list[len];
            if (cell == null) continue;
            _cellPool.Release(cell);
            cell.Actors.Clear();
        }
        _list.Clear();
        _idToIndex.Clear();
    }
    public void Add(ActorBase value)
    {
        var pos = value.GetWorldPosition() - _min;
        int index = getIndexByPos(pos);
        if (index < 0 || index >= _count) return;

        var id = value.InstanceID;
        CellData current;
        if (_idToIndex.ContainsKey(id))
        {
            var old = _idToIndex[id];
            if (index == old)
            {
                return;
            }
            current = _list[old];
            if (current != null)
            {
                current.Actors.Remove(value.InstanceID);
            }
            _idToIndex[id] = index;

        }
        else
        {
            _idToIndex.Add(id, index);
        }
        current = _list[index];
        if (current == null)
        {
            current = _cellPool.Get();
            _list[index] = current;
        }
        current.Actors.Add(value.InstanceID, value);
    }
    public void Remove(ActorBase value)
    {
        var id = value.InstanceID;
        if (!_idToIndex.ContainsKey(id)) return;
        var index = _idToIndex[id];
        _idToIndex.Remove(id);
        CellData current = _list[index];
        if (current != null)
        {
            current.Actors.Remove(value.InstanceID);
        }
    }
    public List<ActorBase> ForeachList = new List<ActorBase>(50);
    public void Foreach(Vector3 position, float radius)
    {
        ForeachList.Clear();
        position -= _min;
        int cx;
        int cy;
        getGrid(position, out cx, out cy);
        var size = Mathf.CeilToInt(radius / this.GridSize);
        for (var i = -size; i <= size; i++)
        {
            for (var j = -size; j <= size; j++)
            {
                var index = getIndex(cx + i, cy + j);
                if (index < 0 || index >= _count) continue;
                CellData current = _list[index];
                if (current == null || current.Actors.Count < 1) continue;
                foreach (var item in current.Actors)
                {
                    if (item.Value.Enabled)
                    {
                        ForeachList.Add(item.Value);
                    }
                }
            }
        }
    }
    public Vector3 GetGroundPosition(Vector3 position, float distance = 10f)
    {
        NavMeshHit hit;
        if (NavMesh.SamplePosition(position, out hit, distance, 1 << NavMesh.GetAreaFromName("Walkable")))
        {
            return hit.position;
        }
        return position;
    }
    public ActorBase GetNearest(ActorBase target, float maxDistance, float maxHeight = 1f)
    {
        if (!target.IsGameObjectEnabled) return null;
        ActorBase nearestTarget = null;
        var minDistance = 10000f;
        var targetPos = target.GetWorldPosition();
        this.Foreach(targetPos, maxDistance);
        var len = this.ForeachList.Count;
        while (len-- > 0)
        {
            var value = this.ForeachList[len];
            if (value == target) continue;
            var tpos = value.GetWorldPosition();
            if (Mathf.Abs(targetPos.y - tpos.y) > maxHeight) continue;
            var dis = Vector3.Distance(targetPos, tpos);
            if (dis < minDistance)
            {
                minDistance = dis;
                nearestTarget = value;
            }
        }
        if (minDistance < maxDistance)
        {
            return nearestTarget;
        }
        return null;
    }
    private int getIndexByPos(Vector3 pos)
    {
        int px = Mathf.FloorToInt(pos.x / this.GridSize);
        int py = Mathf.FloorToInt(pos.z / this.GridSize);
        return getIndex(px, py);
    }
    private int getIndex(int x, int y)
    {
        return y * this.Width + x;
    }
    private void getGrid(Vector3 pos, out int x, out int y)
    {
        x = Mathf.FloorToInt(pos.x / this.GridSize);
        y = Mathf.FloorToInt(pos.z / this.GridSize);
    }
#if UNITY_EDITOR
    public void Draw()
    {
        var inc = 0.05f;
        for (var i = 0; i < this.Width; i++)
        {
            for (var j = 0; j < this.Height; j++)
            {
                var x = i * this.GridSize;
                var z = j * this.GridSize;
                var p00 = new Vector3(x + inc, 0, z + inc) + _min;
                var p11 = new Vector3(x + this.GridSize - inc, 0, z + this.GridSize - inc) + _min;
                var p10 = new Vector3(x + this.GridSize - inc, 0, z + inc) + _min;
                var p01 = new Vector3(x + inc, 0, z + this.GridSize - inc) + _min;
                Debug.DrawLine(p00, p10, Color.blue);
                Debug.DrawLine(p11, p10, Color.blue);
                Debug.DrawLine(p11, p01, Color.blue);
                Debug.DrawLine(p00, p01, Color.blue);
            }
        }
    }
#endif
    private class CellData
    {
        public Dictionary<int, ActorBase> Actors;
        public CellData()
        {
            Actors = new Dictionary<int, ActorBase>(10);
        }
    }
}